/**
 * \file Interrupt.cc
 * \brief Interrupt management facilities
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <ktypes.h>
#include <panic.h>
#include <Sync.h>
#include <platform/RegUtil.h>
#include <platform/Interrupt.h>
#include <platform/Pic.h>

#include <i386/hw-instr.h>
#include <i386/console.h>
#include <stdio.h>

typedef struct _int_node {
	struct _int_node *next, *prev;
	
	int priority;		// interrupt handler priority
	uint8 interrupt;	// interrupt number
	bool mask;			// mask vector on interrupt
	
	InterruptHandler handler;
} int_node;

// linked lists of listeners for each interrupt in the range [0, INTERRUPT_MAX]
static int_node * head[INTERRUPT_MAX+1], * tail[INTERRUPT_MAX+1];

// interrupt handled flags
static bool endOfInterrupt[INTERRUPT_MAX+1];
static InterruptLock eoiLocks[INTERRUPT_MAX+1];

// instrrupt mask counters
static sint32 interruptMask[INTERRUPT_MAX+1];

// locks
static InterruptLock queueLock;
static InterruptLock lock;

static bool UnhandledExceptionHandler(regs_t *regs);

void Interrupt::initialize(void) {
	/*
	 * head, tail, and endOfInterrupt are uninitialized statics,
	 * so they should be located in the bss, which gets zeroed
	 * at startup.
	 */
	
	// register UnhandledExceptionHandler for each procssor exception
	for (uint8 n=0; n<32; n++) {
		// use low priority to allow other handlers to supersede this one
		registerHandler(n, UnhandledExceptionHandler, -1000);
	}
}

void Interrupt::registerHandler(uint8 interrupt, InterruptHandler handler,
	int priority) {
	//printf("%s int: %02x handler: %08x\n", __FUNCTION__, interrupt,
	//	(uint32) handler);
	queueLock.lock();
	
	if (interrupt <= INTERRUPT_MAX) {
		int_node * node = new int_node;
		
		if (node == 0) {
			panic("%s %s:%d Error allocating interrupt list node", __FUNCTION__,
				__FILE__, __LINE__);
		}
		
		// initially enable the interrupt if this is the first handler,
		// otherwise the masking level takes effect
		if (!head[interrupt] && Pic::isOwned(interrupt)) {
			Pic::enable(interrupt, true);
		}
		
		node->handler = handler;
		node->priority = priority;
		node->interrupt = interrupt;
		
		int_node *n;
		for (n=head[interrupt]; n != 0; n = n->next) {
			if (node->priority >= n->priority) {
				node->prev = n->prev;
				n->prev = node;
				
				if (node->prev) {
					node->prev->next = node;
				} else {
					head[interrupt] = node;
				}
				
				node->next = n;
				
				break;
			}
		}
		
		if (n == 0) {
			if (tail[interrupt]) {
				node->prev = tail[interrupt];
				node->next = 0;
				tail[interrupt]->next = node;
				tail[interrupt] = node;
			} else {
				node->prev = 0;
				node->next = 0;
				head[interrupt] = tail[interrupt] = node;
			}
		}
	}
	
	queueLock.unlock();
}

void Interrupt::unregisterHandler(uint8 interrupt, InterruptHandler handler) {
	//printf("%s int: %02x handler: %08x\n", __FUNCTION__, interrupt,
	//	(uint32) handler);
	queueLock.lock();
	
	if (interrupt <= INTERRUPT_MAX) {
		for (int_node *n=head[interrupt]; n != 0; n = n->next) {
			if (n->handler == handler) {
				
				bool isHead = head[interrupt] == n;
				bool isTail = tail[interrupt] == n;
				
				if (isTail) {
					tail[interrupt] = n->prev;
					
					if (!isHead) {
						n->prev->next = 0;
					}
				}
				
				if (isHead) {
					head[interrupt] = n->next;
					
					if (!isTail) {
						n->next->prev = 0;
					}
				}
				
				if (!isHead && !isTail) {
					n->prev->next = n->next;
					n->next->prev = n->prev;
				}
				
				delete n;
				
				// disable the interrupt if this is the last handler
				if (!head[interrupt] && Pic::isOwned(interrupt)) {
					Pic::enable(interrupt, false);
						
					interruptMask[interrupt] = 0;
					if (interrupt >= PIC2_BASE && interrupt <= PIC2_BASE + 7) {
						interruptMask[INT_PIC2] = 0;
					}
				}
			}
		}
	}
	
	queueLock.unlock();
}

void Interrupt::mask(uint8 interrupt) {
	if (Pic::isOwned(interrupt)) {
		lock.lock();
		if (--interruptMask[interrupt] <= 0) {
			Pic::enable(interrupt, false);
		}
		lock.unlock();
	}
}

void Interrupt::unmask(uint8 interrupt) {
	if (Pic::isOwned(interrupt)) {
		lock.lock();
		if (++interruptMask[interrupt] >= 0) {
			// prevent extra unmasks from blocking the next mask
			if (interruptMask[interrupt] > 0) {
				interruptMask[interrupt] = 0;
			}
			
			Pic::enable(interrupt, true);
		}
		lock.unlock();
	}
}

bool Interrupt::isHandled(uint8 interrupt) {
	lock.lock();
	
	bool handled = interrupt <= INTERRUPT_MAX && endOfInterrupt[interrupt];
	
	lock.unlock();
	return handled;
}

void Interrupt::enableLocalInterrupts(void) {
	sti();
}

void Interrupt::disableLocalInterrupts(void) {
	cli();
}

void Interrupt::waitForInterrupt(void) {
	hlt();
}

extern "C"
void interrupt_handler(regs_t *regs) {
	printf_xy(regs->which_int*3, 23, WHITE, "%02x ", regs->which_int);
	//printf_xy(76, 24, BLUE, "%02x ", enterCount);
	//printf_xy(71, 0, WHITE, "%08x ", regs->task_esp);
	
	// clear end of interrupt (EOI) flag for this interrupt
	eoiLocks[regs->which_int].lock();
	endOfInterrupt[regs->which_int] = false;
	eoiLocks[regs->which_int].unlock();
	
	// flag indicating that any kernel handler has handled the interrupt
	bool handled = false;
		
	// iterate through the list of handlers corresponding to the interrupt
	int_node *next = 0;
	for (int_node *n=head[regs->which_int]; n; n = next) {
		// save next node so that handlers can safely unregister themselves
		// without risking the node memory being reused before next iteration
		next = n->next;
		
		handled = n->handler(regs) || handled;
		
		eoiLocks[regs->which_int].lock();
		endOfInterrupt[regs->which_int] = handled;
		eoiLocks[regs->which_int].unlock();
		
		Interrupt::disableLocalInterrupts();
	}
	
	// send EOI to the PIC if the interrupt was handled by any handler and
	// within the hardware range
	if (handled && Pic::isOwned(regs->which_int)) {
		Pic::issueEOI(regs->which_int);
	}
	
	//printf_xy(regs->which_int*3, 23, DARKGRAY, "%02x ", regs->which_int);
}

/*
 * Unhandled exception handler
 * Handles the first 32 interrupts which are processor exceptions, traps,
 * faults, and reserved interrupts.
 *
 * This handler will be added at the lowest priority, which will cause it to
 * only be executed after every other handler for the same interrupt. This
 * handler will check to make sure that another handler has not handled the
 * interrupt (marked by issuing EOI) before panicking the kernel.
 */

static bool UnhandledExceptionHandler(regs_t *regs) {
	static char * exception_name[] = {
		"Divide Error", "Debug", "NMI Interrupt", "Breakpoint", "Overflow",
		"BOUND Range Exceeded", "Invalid Opcode", "No Math Coprocessor",
		"Double Fault", "Coprocessor Segment Overrun", "Invalid TSS",
		"Segment Not Present", "Stack Segment Fault", "General Protection",
		"Page Fault", "Reserved", "Floating-Point Error", "Alignment Check",
		"Machine Check", "SIMD Floating-Point Exception", "Reserved",
		"Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved",
		"Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved"
	};
	
	if (!endOfInterrupt[regs->which_int]) {
		Interrupt::disableLocalInterrupts();
		
		_clear(' ', 0x17, 0, 0, 79, 24);
		cursor(16, 16);
		window(0, 0, 79, 24);
		
		// see if we're in a vm86 context
		bool vm86 = regs->eflags & 0x20000;
		regs_vm86_t *vmregs = (regs_vm86_t *) regs;
		
		// spread the printing out to prevent stack overflows
		uint32 *stack = (uint32 *) regs->task_esp; 
		printf("Kernel panic!\n"
			" CS:     %04x EIP: %08x EFL: %08x CR2: %08x\n\n",
			regs->cs, regs->eip, regs->eflags, getCR2());
		printf(
			"EAX: %08x ECX: %08x EDX: %08x EBX: %08x\n"
			"ESP: %08x EBP: %08x ESI: %08x EDI: %08x\n\n",
			regs->eax, regs->ecx, regs->edx, regs->ebx,
			vm86 ? regs->user_esp : regs->esp, // vm86 mode?
			regs->ebp, regs->esi, regs->edi);
		printf(
			" DS:     %04x  ES:     %04x  FS:     %04x  GS:     %04x\n"
			" SS:     %04x\n\n"
			"Unhandled exception %02x: %s at %08x code %08x\n\n",
			vm86 ? vmregs->vm_ds : regs->ds, vm86 ? vmregs->vm_es : regs->es,
			vm86 ? vmregs->vm_fs : regs->fs, vm86 ? vmregs->vm_gs : regs->gs,
			vm86 ? regs->user_ss : 0xffff,
			regs->which_int, exception_name[regs->which_int], regs->eip,
			regs->err_code);
		printf(
			"Stack trace:\n"
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n",
			stack[0], stack[1], stack[2], stack[3], stack[4], stack[5],
			stack[6], stack[7]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n",
			stack[8], stack[9], stack[10], stack[11],
			stack[12], stack[13], stack[14], stack[15]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n",
			stack[16], stack[17], stack[18], stack[19], stack[20], stack[21],
			stack[22], stack[23]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n",
			stack[24], stack[25], stack[26], stack[27], stack[28], stack[29],
			stack[30], stack[31]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n",
			stack[32], stack[33], stack[34], stack[35], stack[36], stack[37],
			stack[38], stack[39]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[40],
			stack[41], stack[42], stack[43], stack[44], stack[45], stack[46],
			stack[47]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[40],
			stack[41], stack[42], stack[43], stack[44], stack[45], stack[46],
			stack[47]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[48],
			stack[48], stack[50], stack[51], stack[52], stack[53], stack[54],
			stack[55]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[56],
			stack[57], stack[58], stack[59], stack[60], stack[61], stack[62],
			stack[63]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[64],
			stack[65], stack[66], stack[67], stack[68], stack[69], stack[70],
			stack[71]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x\n", stack[72],
			stack[73], stack[74], stack[75], stack[76], stack[77], stack[78],
			stack[79]);
		printf(
			"    %08x %08x %08x %08x %08x %08x %08x %08x", stack[80],
			stack[81], stack[82], stack[83], stack[84], stack[85], stack[86],
			stack[87]);
		
		Pic::disableAllIRQs();
		hlt();
	}
	
	// don't signal EOI
	return false;
}
